#ifndef __MULTI_FILE_BUNDLE_IMPL__
#define __MULTI_FILE_BUNDLE_IMPL__

#include <map>
#include <string>

namespace xm
{
	namespace mfb
	{
		struct noud
		{};

		struct ifsect_t : refable_impl_t
		{
			std::string		name;
			uint64_t		offset;
			uint32_t		bytes;
			uint32_t		extcb;
			uint8_t*		extdt;
			ifsect_t() : extdt(0),offset(0),bytes(0),extcb(0)
			{
			}
			~ifsect_t() 
			{
				if( extdt ) delete extdt;
			}
		};

		struct index_by_name
		{
			refp<ifsect_t> op;
			const char*		np;
			index_by_name( ifsect_t * pp ) : op(pp), np(0)
			{}
			index_by_name( const char* ap ) : np(ap)
			{}
			const char * nameptr() const
			{
				if( op ) return op->name.c_str();
				return np;
			}
			bool operator < ( const index_by_name & right ) const
			{
				return strcmp( nameptr(), right.nameptr() ) < 0;
			}
		};

		template < class T = noud >
		struct reader_t
		{
			struct _ifsec_t : ifsect_t
			{
				T ud;
			};

			typedef _ifsec_t	sect_t;

			typedef std::map< index_by_name, refp<_ifsec_t> > sect_table_t;

			fhdr_t			_fil_hdr;

			sect_table_t	_sections;

			long open( IUnknown * sop )
			{
				if( !sop ) return E_INVALIDARG;

				iptr<IKBStream> kbs( sop );
				if( !kbs ) return E_NOINTERFACE;

				size_t eff = 0;
				RFAILED( kbs->Readp( 0, &_fil_hdr, sizeof(_fil_hdr), &eff ) );
				
				if( _fil_hdr.tags[0] != 'X' || _fil_hdr.tags[1] != 'F' ||
					_fil_hdr.tags[2] != 'B' || _fil_hdr.tags[3] != '!' )
					return E_FAIL;
				
				if( _fil_hdr.ffver > 0 ) 
					return E_FAIL;

				if( _fil_hdr.coder )
					return E_FAIL;

				uint64_t offset = 0;

				for( size_t i = 0; i < _fil_hdr.files; ++ i )
				{
					refp<_ifsec_t> section = new _ifsec_t;
					if( !section ) return E_OUTOFMEMORY;

					fsect_t sect = {};
					RFAILED( kbs->Read( &sect, sizeof(sect), &eff ) );

					section->offset = offset;
					section->bytes = sect.bytes;

					// read string;
					if( sect.namelen )
					{
						UTIL::sentry<char*> namp = new char[sect.namelen];
						if( !namp ) return E_OUTOFMEMORY;

						RFAILED( kbs->Read( namp, sect.namelen, &eff ) );
						if( eff != sect.namelen )
							return E_FAIL;

						namp[eff-1] = 0;
						section->name = namp;
					}

					// read extcb
					if( sect.extcb )
					{
						UTIL::sentry<uint8_t*> bbp = new uint8_t[sect.extcb];
						if( !bbp ) return E_OUTOFMEMORY;

						RFAILED( kbs->Read( bbp, sect.extcb, &eff ) );
						if( eff != sect.extcb )
							return E_FAIL;

						section->extcb = sect.extcb;
						section->extdt = bbp.detach();
					}

					offset += sect.bytes;

					sect_table_t::value_type val( index_by_name(section), section );
					
					sect_table_t::_Pairib ib = _sections.insert( val );
					if( !ib.second )
						return E_FAIL;
				}

				uint64_t off = 0;
				RFAILED( kbs->Tell( &off ) );

				sect_table_t::iterator it = _sections.begin();
				for( ; it != _sections.end(); ++ it )
				{
					refp<_ifsec_t> sect = it->second;
					sect->offset += off;
				}

				return S_OK;
			}
		
			long close()
			{
				_sections.clear();
			}

			refp<_ifsec_t> find_section( const char * name )
			{
				if( !name ) return NULL;

				sect_table_t::iterator it = _sections.find( name );

				if( it == _sections.end() ) 
					return NULL;

				return it->second;
			}
		};


		struct writer
		{
			struct w_sect_t
			{
				iptr<IKBStream>			dat;
				std::vector<uint8_t>	extdat;
				uint8_t					opt8;
				w_sect_t() : opt8(0) {}
			};

			typedef std::map< std::string, w_sect_t > w_sect_map_t;

			w_sect_map_t	_tables;
			fhdr_t			_fil_hdr;

			writer()
			{
				memset( &_fil_hdr, 0, sizeof(_fil_hdr) );
			}

			long set_file_info( const char * text )
			{
				memset( _fil_hdr.info, 0, sizeof(_fil_hdr.info) );
				strncpy( (char*)_fil_hdr.info, text, sizeof(_fil_hdr.info) );
				return 0;
			}

			long set_table_data( const char * tab_name, IUnknown* sp )
			{
				if( !tab_name ) return E_FAIL;
				iptr<IKBStream> kso( sp );
				if( sp && !kso ) return E_NOINTERFACE;
				_tables[tab_name].dat = kso;
				return S_OK;
			}

			long set_table_opt( const char * tab_name, uint8_t opt )
			{
				if( !tab_name ) return E_FAIL;
				_tables[tab_name].opt8 = opt;
				return S_OK;
			}

			long set_table_extdat( const char * tab_name, const void * pdat, size_t cbdat )
			{
				if( !tab_name ) return E_FAIL;
				if( cbdat > 0xFF ) return E_UNEXPECTED;
				const uint8_t* bf = (const uint8_t*)pdat; 
				const uint8_t* be = bf + cbdat;
				_tables[tab_name].extdat.assign( bf, be );
				return S_OK;
			}

			long commit( IUnknown * upo )
			{
				if( !upo ) return E_INVALIDARG;

				iptr<IKBStream> outp( upo );
				if( !outp ) return E_NOINTERFACE;

				size_t eff = 0;

				_fil_hdr.tags[0] = 'X'; _fil_hdr.tags[1] = 'F';
				_fil_hdr.tags[2] = 'B'; _fil_hdr.tags[3] = '!';

				_fil_hdr.ffver = 0;
				_fil_hdr.chksum = 0;
				_fil_hdr.coder = 0;
				_fil_hdr.opts = 0;
				_fil_hdr.files = _tables.size();			

				RFAILED( outp->SetPos( sizeof(_fil_hdr) ) );

				uint64_t dc_total = 0;
				size_t   st_total = 0;
				w_sect_map_t::iterator it = _tables.begin();

				for( ; it != _tables.end(); ++ it )
				{
					std::string tname = it->first;
					w_sect_t & ws = it->second;
					uint64_t fs = 0;
					if( ws.dat ) 
					{
						fs = ws.dat->Length();
						if( fs == LENGTH_ERROR )
							return E_FAIL;
					}

					if( fs > (uint32_t)-1 )
						return E_UNEXPECTED;

					size_t namlen = tname.size() + 1;

					dc_total += fs;
					st_total += (sizeof(fsect_t) + ws.extdat.size() + namlen);

					fsect_t fsec;
					fsec.namelen = namlen;
					fsec.extcb = ws.extdat.size();
					fsec.bytes = (uint32_t)fs;
					fsec.opts = ws.opt8;

					RFAILED( outp->Write( &fsec, sizeof(fsec), &eff ) );
					if( sizeof(fsec) != eff ) return E_FAIL;

					RFAILED( outp->Write( tname.data(), namlen, &eff ) );
					if( namlen != eff ) return E_FAIL;

					if( fsec.extcb )
					{
						RFAILED( outp->Write( ws.extdat.data(), fsec.extcb, &eff ) );
						if( fsec.extcb != eff ) return E_FAIL;
					}
				}

				_fil_hdr.dbc_enc = dc_total;
				_fil_hdr.dbc_dec = dc_total;

				it = _tables.begin();
				for( ; it != _tables.end(); ++ it )
				{
					w_sect_t & ws = it->second;
					if( !ws.dat ) continue;
					RFAILED( ws.dat->SetPos(0) );
					RFAILED( CopyTo<0x2000>( ws.dat, outp ) );
				}

				RFAILED( outp->Writep( 0, &_fil_hdr, sizeof(_fil_hdr), &eff ) );
				RASSERT( eff == sizeof(_fil_hdr), E_FAIL );

				return S_OK;
			}

		};
	
	};


};


#endif